// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Actors.Utilities;
using System.Threading;

namespace TestActorMethods
{
    [TestClass]
    public class AsyncAREUnitTests
    {
        private static volatile int s_counter = 0;
        private static volatile int s_exceptions = 0;

        [TestMethod]
        public void AsyncARE_Test()
        {
            int ITERATIONS = 10;
            int EXTRAS = 5;
            var are = new AsyncAutoResetEvent();
            var mres = new ManualResetEventSlim(false); // used to signal producer that consumer has fired
            CountdownEvent cde = new CountdownEvent(EXTRAS);

            // Launch some async consumers
            for (int i = 0; i < ITERATIONS + EXTRAS; i++) AsyncAREConsumer(are, mres, cde); // fire-and-forget

            // Test that one waiter is released at a time when the ARE is signaled
            for (int i = 0; i < ITERATIONS; i++)
            {
                are.Set();
                mres.Wait(1000);
                Assert.IsTrue(mres.IsSet, "Expected ARE.Set-consequence within 1 sec");
                Assert.IsTrue(s_counter == i + 1, "Expected s_counter to be incremented by one");
                mres.Reset();
            }

            Assert.IsTrue(s_counter == ITERATIONS, "Expected " + ITERATIONS + " consumers to have progressed");
            Assert.IsTrue(s_exceptions == 0, "Did not expect any exceptions to have occurred before ARE disposal");
            are.Dispose(); // Should cause the remaining EXTRAS waiters to be faulted
            cde.Wait(2000); // Should be signaled when the final waiter is faulted
            Assert.IsTrue(cde.IsSet, "Expected the exception-countdown to reach 0 within 2 seconds");
            Assert.IsTrue(s_exceptions == EXTRAS, "Didn't see the expected number of exceptions");

            // Test that waiting on a pre-signaled ARE returns
            are = new AsyncAutoResetEvent();
            are.Set();
            var t1 = are.WaitAsync();
            t1.Wait(1000);
            Assert.IsTrue(t1.IsCompleted, "Expected to be able to return from WaitAsync on pre-signalled ARE");

            // Test disposal functionality
            t1 = are.WaitAsync();
            Assert.IsFalse(t1.IsCompleted, "Did not expect waiter to complete before Dispose");
            are.Dispose();
            Assert.IsTrue(t1.IsFaulted, "Expected waiter to be faulted after Dispose");
            var baseException = t1.Exception.GetBaseException();
            Assert.IsTrue(baseException is ObjectDisposedException, "Expected waiter to have faulted with ODE after Dispose");

            Exception e = null;
            try { are.Set(); }
            catch (Exception exc) { e = exc; }
            Assert.IsTrue(e != null, "Expected exception on Set-after-Dispose");
            Assert.IsTrue(e is ObjectDisposedException, "Expected ODE on Set-after-Dispose");

            e = null;
            try { are.WaitAsync(); }
            catch (Exception exc) { e = exc; }
            Assert.IsTrue(e != null, "Expected exception on WaitAsync-after-Dispose");
            Assert.IsTrue(e is ObjectDisposedException, "Expected ODE on WaitAsync-after-Dispose");
        }

        private async void AsyncAREConsumer(AsyncAutoResetEvent are, ManualResetEventSlim mres, CountdownEvent cde)
        {
            try
            {
                await are.WaitAsync();
                Interlocked.Increment(ref s_counter); // increment the counter
                mres.Set(); // notify the main thread that we are done
            }
            catch (ObjectDisposedException)
            {
                Interlocked.Increment(ref s_exceptions); // increment our exception count
                cde.Signal();
            }
        }
    }
}
